package com.jiangli.utils;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.HashMap;
/**
 * 基于StringRedisTemplate的分布式锁工具类
 * 专门处理字符串类型，避免序列化问题，适合多服务器环境
 */
@Component
@Slf4j
public class RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    // 存储当前线程持有的所有锁信息：key=锁的key，value=LockInfo（包含锁值和重入次数）
    private final ThreadLocal<Map<String, LockInfo>> threadLocalLocks = ThreadLocal.withInitial(HashMap::new);
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);

    // 常量定义（与之前保持一致）
    private static final long DEFAULT_LOCK_EXPIRE = 30;
    private static final int DEFAULT_RETRY_COUNT = 3;
    private static final long DEFAULT_RETRY_INTERVAL = 100;
    private static final long RENEW_INTERVAL = 10;
    private static final String UNLOCK_LUA_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";


    /**
     * 执行带锁的任务（支持同一线程多次获取锁）
     */
    public <T> T executeWithLock(String lockKey, Supplier<T> supplier) {
        return executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_INTERVAL, supplier);
    }

    public <T> T executeWithLock(String lockKey, long lockExpire, int retryCount,
                                 long retryInterval, Supplier<T> supplier) {
        // 1. 检查当前线程是否已持有该锁（可重入判断）
        Map<String, LockInfo> threadLocks = threadLocalLocks.get();
        LockInfo existingLock = threadLocks.get(lockKey);

        if (existingLock != null) {
            // 1.1 已持有该锁，直接增加重入次数，无需重新获取
            log.info("重入次数加一");
            existingLock.incrementReentrantCount();
            try {
                return supplier.get(); // 直接执行任务
            } finally {
                // 1.2 重入次数减1，只有当次数为0时才释放锁
                existingLock.decrementReentrantCount();
                if (existingLock.getReentrantCount() == 0) {
                    threadLocks.remove(lockKey); // 从线程本地缓存移除
                    existingLock.getRenewFuture().cancel(true); // 取消续期
                    unlock(lockKey, existingLock.getLockValue()); // 实际释放锁
                }
            }
        }

        // 2. 未持有锁，正常获取新锁
        String lockValue = UUID.randomUUID().toString();
        ScheduledFuture<?> renewFuture = null;
        boolean isLocked = false;

        try {
            // 2.1 尝试获取锁（带重试）
            isLocked = tryLock(lockKey, lockValue, lockExpire, retryCount, retryInterval);
            if (!isLocked) {
                log.info("获取锁失败{}", lockKey);
                return null; // 获取锁失败
            }

            // 2.2 记录锁信息（重入次数初始为1）
            LockInfo newLock = new LockInfo(lockKey, lockValue, 1);
            threadLocks.put(lockKey, newLock);

            // 2.3 启动续期任务
            renewFuture = scheduler.scheduleAtFixedRate(
                    () -> renewLock(lockKey, lockValue, lockExpire),
                    RENEW_INTERVAL, RENEW_INTERVAL, TimeUnit.SECONDS
            );
            newLock.setRenewFuture(renewFuture);

            // 2.4 执行任务
            return supplier.get();

        } finally {
            // 2.5 只有成功获取锁且重入次数为0时，
            if (isLocked) {
                LockInfo currentLock = threadLocks.get(lockKey);
                if (currentLock != null) {
                    // 关键修复：无论是否重入，先递减一次
                    currentLock.decrementReentrantCount();

                    if (currentLock.getReentrantCount() == 0) {
                        threadLocks.remove(lockKey);
                        if (renewFuture != null) renewFuture.cancel(true);
                        unlock(lockKey, lockValue);
                    }
                }
            }
        }
    }

    // 尝试获取锁（带重试）
    private boolean tryLock(String lockKey, String lockValue, long expire,
                            int retryCount, long retryInterval) {
        boolean locked = tryLockOnce(lockKey, lockValue, expire);
        int currentRetry = 0;
        while (!locked && currentRetry < retryCount) {
            try {
                Thread.sleep(retryInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
            locked = tryLockOnce(lockKey, lockValue, expire);
            currentRetry++;
        }
        return locked;
    }

    // 单次获取锁
    private boolean tryLockOnce(String lockKey, String lockValue, long expire) {
        try {
            return Boolean.TRUE.equals(stringRedisTemplate.opsForValue()
                    .setIfAbsent(lockKey, lockValue, expire, TimeUnit.SECONDS));
        } catch (Exception e) {
            return false;
        }
    }

    // 锁续期
    private void renewLock(String lockKey, String lockValue, long expire) {
        try {
            String currentValue = stringRedisTemplate.opsForValue().get(lockKey);
            if (lockValue.equals(currentValue)) {
                log.info("{}续锁",lockKey);
                stringRedisTemplate.expire(lockKey, expire, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            // 续期失败不影响主流程
        }
    }

    // 释放锁
    private void unlock(String lockKey, String lockValue) {
        try {
            DefaultRedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_LUA_SCRIPT, Long.class);
            stringRedisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
        } catch (Exception e) {
            // 释放失败记录日志
        }
    }

    /**
     * 锁信息类（扩展重入次数和续期任务）
     */
    private static class LockInfo {
        private final String lockKey;
        @Getter
        private final String lockValue;
        // getter和setter
        @Getter
        private int reentrantCount; // 重入次数
        @Setter
        @Getter
        private ScheduledFuture<?> renewFuture; // 续期任务

        public LockInfo(String lockKey, String lockValue, int reentrantCount) {
            this.lockKey = lockKey;
            this.lockValue = lockValue;
            this.reentrantCount = reentrantCount;
        }

        // 重入次数增减
        public void incrementReentrantCount() {
            this.reentrantCount++;
        }

        public void decrementReentrantCount() {
            this.reentrantCount--;
        }

    }

    public void destroy() {
        scheduler.shutdown();
    }
}